package com.marketlive.system.config;

import org.marketlive.system.config.IConfigurationBranch;
import org.springframework.beans.BeansException;

/*
(C) Copyright MarketLive. 2006. All rights reserved.
MarketLive is a trademark of MarketLive, Inc.
Warning: This computer program is protected by copyright law and international treaties.
Unauthorized reproduction or distribution of this program, or any portion of it, may result
in severe civil and criminal penalties, and will be prosecuted to the maximum extent
possible under the law.
*/

import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.ArrayList;
import java.util.Properties;


/**
 * Replaces any token values with their literal values.  For example, these values in tree:
 * <pre>
 * marketlive.root=C:/marketlive
 * marketlive.conf_root=${marketlive.root}/conf
 * </pre>
 * will result in:
 * <pre>
 * marketlive.conf_root=C:/marketlive/conf
 * </pre>
 */

public class TokenResolver {
    /**
     * Logger
     */
    private static Log log = LogFactory.getLog(TokenResolver.class);

    /** Token prefix and suffix identifiers */
    public static final String DEFAULT_PLACEHOLDER_PREFIX = "${";
    public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}";
            private String placeholderPrefix = DEFAULT_PLACEHOLDER_PREFIX;
            private String placeholderSuffix = DEFAULT_PLACEHOLDER_SUFFIX;
    private boolean ignoreUnresolvablePlaceholders = true;

    /**
     * Resolves all references in the configuration.
     *
     * @param startingBranch the point from which to start
     */
    public void resolve(ConfigurationBranch startingBranch) {
        log.info("TokenResolver::resolve() called");
        
        /* TODO: set up for log.verbose() * /
        java.util.Enumeration en = System.getProperties().propertyNames();
        while (en.hasMoreElements()) {
        	String name = en.nextElement().toString();
        	if (name.startsWith("marketlive")) {
        		String value = System.getProperty(name);
        		System.out.println("There is a 'marketlive' System property named '" + name + "' with value '" + value + "'");
        	}
        }
        System.out.println("--------------------------------");
        /* */
        
        // load the properties
       ArrayList keys = new ArrayList();
       startingBranch.getLeafNames(keys,null);

       for (int i=0; i<keys.size(); i++) {
           String name = (String) keys.get(i);
           ConfigurationLeaf leaf = (ConfigurationLeaf) startingBranch.getLeaf(name);
           String startingValue = leaf.getAsString();
    	   //System.out.println("leaf [" + name + "]'s startingValue = '" + startingValue + "'");
           String parsedValue = parseString(startingBranch, startingValue, startingValue);

           if (parsedValue != null && !parsedValue.equals(startingValue)) {
               log.info("Resolving leaf: " + name + " to " + startingValue + " --> " + parsedValue );
               //System.out.println("Resolving leaf [" + name + "] from '" + startingValue + "' --> '" + parsedValue + "'");
               leaf.setAsString(parsedValue);
           }
           /* * /
           else {
        	   System.out.println("leaf [" + name + "] left at '" + startingValue + "'");
           }
           /* */
       }
    }

    /**
     * Parses values recursively to be able to resolve cross-references between placeholder values.
     * @param rootBranch the root of the configuration
     * @param strVal the string to search through
     * @param originalPlaceholder the token to replace 
     * @return the string with tokens replaced
     * @throws BeansException
     */
    protected String parseString(ConfigurationBranch rootBranch, String strVal, String originalPlaceholder)
        throws BeansException {
 
        StringBuffer buf = new StringBuffer(strVal);

        int startIndex = strVal.indexOf(this.placeholderPrefix);
        while (startIndex != -1) {
            int endIndex = buf.toString().indexOf(
                this.placeholderSuffix, startIndex + this.placeholderPrefix.length());
            if (endIndex != -1) {
            	/*
            	log.info("Found token in the property string '" + strVal + "'");
            	System.out.println("Found token in the property string '" + strVal + "'");
            	*/
                String placeholder = buf.substring(startIndex + this.placeholderPrefix.length(), endIndex);
                String originalPlaceholderToUse = null;

                if (originalPlaceholder != null) {
                    originalPlaceholderToUse = originalPlaceholder;
                    if (placeholder.equals(originalPlaceholder)) {
                        throw new BeanDefinitionStoreException(
                            "Circular placeholder reference: " + placeholder);
                    }
                }
                else {
                    originalPlaceholderToUse = placeholder;
                }

                String propVal = resolveConfigurationVariable(rootBranch,placeholder);
                if (propVal==null) {
                    propVal = resolveEnvironmentVariable(placeholder);
                }
                if (propVal==null) {
                    log.info("Can not resolve placeholder: " + placeholder);
                    //System.out.println("Can not resolve placeholder: " + placeholder);
                }

                if (propVal != null) {
                    propVal = parseString(rootBranch, propVal, originalPlaceholderToUse);
                    if (log.isDebugEnabled()) {
                        log.debug("Resolving placeholder '" + placeholder + "' to [" + propVal + "]");
                    }
                    //System.out.println("Resolving placeholder '" + placeholder + "' to [" + propVal + "]");
                    buf.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
                    startIndex = buf.toString().indexOf(this.placeholderPrefix, startIndex + propVal.length());
                }
                else if (this.ignoreUnresolvablePlaceholders) {
                    // proceed with unprocessed value
                    startIndex = buf.toString().indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
                }
                else {
                    throw new BeanDefinitionStoreException("Could not resolve placeholder '" + placeholder + "'");
                }
            }
            else {
                startIndex = -1;
            }
        }

        return buf.toString();
    }

    /**
     * Returns the value for the configuration path or null if the path does not exist.
     * 
     * @param rootBranch
     * @param path
     * @return string value associated with the path
     */
    private String resolveConfigurationVariable(IConfigurationBranch rootBranch, String path) {
        try {
            return rootBranch.getAsString(path);
        }
        catch (Exception e) {
            return null;
        }
    }

    /**
     * Returns the environent variable or null if the variable has not been set.
     * 
     * @param path
     * @return string environment variable
     */
    private String resolveEnvironmentVariable(String path) {
        String prop = System.getProperty(path);
        //System.out.println("resolveEnvironmentVariable: key = '" + path + "'; value = '" + prop + "'");
        return prop;
    }
}
